package com.lookup.netty;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.multipart.*;
import io.netty.handler.stream.ChunkedStream;
import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCountUtil;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder;

import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.List;
import java.util.Set;

import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
import static io.netty.handler.codec.http.HttpResponseStatus.INTERNAL_SERVER_ERROR;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;

public class ServletNettyHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext channelHandlerContext, Object msg) throws Exception {
        try {
            if (msg instanceof FullHttpRequest) {
                FullHttpRequest fullHttpRequest = (FullHttpRequest) msg;
                if (!fullHttpRequest.decoderResult().isSuccess()) {
                    sendError(channelHandlerContext, BAD_REQUEST);
                    return;
                }
                DefaultHttpHeaders defaultHttpHeaders = (DefaultHttpHeaders) fullHttpRequest.headers();
                org.springframework.http.HttpHeaders headers = new org.springframework.http.HttpHeaders();
                Set<String> names = defaultHttpHeaders.names();
                for (String name : names) {
                    headers.add(name, defaultHttpHeaders.get(name));
                }
                ResultActions resultActions = null;
                // 请求HTTP GET
                if (fullHttpRequest.method().equals(HttpMethod.GET)) {
                    MockHttpServletRequestBuilder builder = get(fullHttpRequest.uri());
                    builder.headers(headers);
                    resultActions = mockMvc.perform(builder);
                }
                // 请求HTTP POST
                else if (fullHttpRequest.method().equals(HttpMethod.POST)) {
                    HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(new DefaultHttpDataFactory(), fullHttpRequest);
                    decoder.offer(fullHttpRequest);
                    if (!decoder.isMultipart()) {
                        MockHttpServletRequestBuilder builder = post(fullHttpRequest.uri());
                        builder.headers(headers);
                        List<InterfaceHttpData> parmList = decoder.getBodyHttpDatas();
                        for (InterfaceHttpData parma : parmList) {
                            if (parma.getHttpDataType() == InterfaceHttpData.HttpDataType.Attribute) {
                                Attribute data = (Attribute) parma;
                                builder.param(data.getName(), data.getValue());
                            }
                        }
                        resultActions = mockMvc.perform(builder);
                    } else {
                        MockMultipartHttpServletRequestBuilder builder = fileUpload(fullHttpRequest.uri());
                        builder.headers(headers);

                        List<InterfaceHttpData> parmList = decoder.getBodyHttpDatas();
                        for (InterfaceHttpData parma : parmList) {
                            if (parma.getHttpDataType() == InterfaceHttpData.HttpDataType.FileUpload) {
                                MixedFileUpload file = (MixedFileUpload) parma;
                                builder.file(new MockMultipartFile(file.getName(), file.getFilename(), file.getContentType(), file.get()));
                            }
                        }
                        resultActions = mockMvc.perform(builder);
                    }
                }

                if (resultActions!=null&&resultActions.andReturn()!=null)
                {
                    HttpServletResponse servletResponse = resultActions.andReturn().getResponse();
                    HttpResponseStatus status = HttpResponseStatus.valueOf(servletResponse.getStatus());
                    DefaultHttpResponse response = new DefaultHttpResponse(HTTP_1_1, status);

                    for (String name : servletResponse.getHeaderNames()) {
                        response.headers().add(name, servletResponse.getHeaders(name));
                    }
                    // Write the initial line and the header.
                    channelHandlerContext.write(response);

                    MockHttpServletResponse mockHttpServletResponse = (MockHttpServletResponse) servletResponse;
                    InputStream contentStream = new ByteArrayInputStream(mockHttpServletResponse.getContentAsByteArray());

                    // Write the content and flush it.
                    ChannelFuture writeFuture = channelHandlerContext.writeAndFlush(new ChunkedStream(contentStream));
                    writeFuture.addListener(ChannelFutureListener.CLOSE);
                }

            }
        } finally {
            ReferenceCountUtil.release(msg);
        }
    }

    private MockMvc mockMvc;

    public ServletNettyHandler(MockMvc mockMvc) {
        this.mockMvc = mockMvc;
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        cause.printStackTrace();
        if (ctx.channel().isActive()) {
            sendError(ctx, INTERNAL_SERVER_ERROR);
        }
    }

    private static void sendError(ChannelHandlerContext ctx,
                                  HttpResponseStatus status) {
        ByteBuf content = Unpooled.copiedBuffer("Failure: " + status.toString()
                + "\r\n", CharsetUtil.UTF_8);

        FullHttpResponse fullHttpResponse = new DefaultFullHttpResponse(
                HTTP_1_1, status, content);
        fullHttpResponse.headers().add("ContextType", "text/plain; charset=UTF-8");
        ctx.write(fullHttpResponse).addListener(ChannelFutureListener.CLOSE);
    }

}
